PostgreSQL HOT与索引扫描

1 安装pageinspece 插件

PostgreSQL SQL执行计划与调优

2 Heap Only Tuple Scans(仅堆元组扫描)

2.1 HOT的特点与好处

•被更新的行存储在与旧行同一数据块中。
•可以有效的使用索引和表的块。
•可以降低VACUUM的必要性。

2.2 Update a Row Without HOT

  1. 没有HOT 特性时,隐藏旧行,插入新行,并且索引需要添加对应元组指向新的块。即便不是更新索引列,也会更新索引

2.3 How HOT Performs(Update a row with HOT)


使用HOT 特性时,索引不会进行维护,Tuple1 的ctid 指向Tpule2 ,这样索引就不需要维护。降低开销。
但是有一个问题, 取数据的时候需要扫描两行,增大开销。

2.4 How HOT Performs(Pruning of the line pointers)(修剪行指针)

2.5 How HOT Performs(Defragmentation of the dead tuples)空间整理

·   ·   ·
·   ·...
IndexTuple
IndexTuple
index
index
Key,TID=(block=5,Offset=1)
Key,TID=(block=5,Offset=1)
·   ·   ·
·   ·...
page
8192(byte)
page...
page
8192(byte)
page...
0th
0th
1th
1th
Page Header
Page Header
1
1
tuple 1
tuple 1
tuple 2
tuple 2
2
2
VACUMM
VACUMM
page
8192(byte)
page...
Redirect
Redirect
·   ·   ·
·   ·...
IndexTuple
IndexTuple
index
index
Key,TID=(block=0,Offset=2)
Key,TID=(block=0,Offset=2)
·   ·   ·
·   ·...
page
8192(byte)
page...
0th
0th
1th
1th
Page Header
Page Header
1
1
tuple 2
tuple 2
2
2
Text
Text
page
8192(byte)
page...
Redirect
Redirect
(1)VACUUM之前
(1)VACUUM之前
(2) VACUUM之后
(2) VACUUM之后
page
8192(byte)
page...
2th
2th
2th
2th
Text is not SVG - cannot display

2.5.1 索引未修剪

  1. 索引访问索引元组。
  2. 获取ctid,指向(5,1)。
  3. item1指向Tuple1
  4. Tuple1 重新指向 Tuple2
  5. 完成回表动作。

2.5.2 索引修剪后

  1. 索引访问索引元组,取出ctid。
  2. item1 指向item2
  3. item2 指向Tuple2
  4. 完成回表动作。

2.6 HOT 执行过程

  1. 更新(5,1) 为(5,2) 能够指向Tuple2,t_informaask2 为HEAP_HOT_UPDATED
  2. 插入新行(5,2),t_informaask2 为HEAP_HOT_UPDATED

infomask2(2bytes)

[xdb@localhost ~]$ hexdump -C $PGDATA/base/13758/16429 -s 8170 -n 2
00001fea 03 00 |..|
00001fec
//t_infomask2=\x0003,3代表什么意思?我们看看t_infomask2的说明
/*
* information stored in t_infomask2:
 */
 #define HEAP_NATTS_MASK 0x07FF /* 11 bits for number of attributes */
 /* bits 0x1800 are available */
 #define HEAP_KEYS_UPDATED 0x2000 /* tuple was updated and key cols
 * modified, or tuple deleted */
 #define HEAP_HOT_UPDATED 0x4000 /* tuple was HOT-updated */
 #define HEAP_ONLY_TUPLE 0x8000 /* this is heap-only tuple */
 #define HEAP2_XACT_MASK 0xE000 /* visibility-related bits */
 //根把十六进制值转换为二进制显示
 11111111111 #define HEAP_NATTS_MASK 0x07FF
 10000000000000 #define HEAP_KEYS_UPDATED 0x2000
 100000000000000 #define HEAP_HOT_UPDATED 0x4000
 1000000000000000 #define HEAP_ONLY_TUPLE 0x8000
 1110000000000000 #define HEAP2_XACT_MASK 0xE000
 1111111111111110 #define SpecTokenOffsetNumber 0xfffe

 //前(低)11位为属性(字段 )的个数,3意味着有3个属性(字段)

2.7 HOT 不可用的情况(有问题)

1. 索引块占满情况。
2. 更新了主键列(更新主键列也会进行HOT)。
实验未成功,

3 仅索引扫描(Index-Only Scans)

  1. 取值都由索引提供,从这方面来讲是不需要回表的,但是由于仅索引扫描需要判断元组可见性,可见性由 tmin,tmax决定,而 索引不包含这些信息,所以 必须进行回表。造成性能降低。
  2. 为了提高性能,所以pg 引入了Visibility map 去判断是否元组是否可见。

3.1 判断流程

  1. Visibility Map 0th = 1 所有元组都可见,不需要回表扫描。
  2. Visibility Map 0th = 0 部分元组不可见,需要回表扫描。

4 实验

4.1 环境准备

create table t01(id int);
 create index  t01_idx_id ON  t01 (id);
  insert into t01 values(1);
postgres=# SELECT lp as tuple, t_xmin, t_xmax, t_field3 as t_cid, t_ctid 
                FROM heap_page_items(get_raw_page('t01', 0));
 tuple | t_xmin | t_xmax | t_cid | t_ctid 
-------+--------+--------+-------+--------
     1 |    869 |      0 |     0 | (0,1)
(1 row)

4.2 UPDATE

postgres=# update t01 set id=2 where id=1;
UPDATE 1
postgres=# SELECT lp as tuple, t_xmin, t_xmax, t_field3 as t_cid, t_ctid 
                FROM heap_page_items(get_raw_page('t01', 0));
 tuple | t_xmin | t_xmax | t_cid | t_ctid 
-------+--------+--------+-------+--------
     1 |    869 |    871 |     0 | (0,2)
     2 |    871 |      0 |     0 | (0,2)

4.3 VCACCUM

postgres=# vacuum t01;

4.4 查看索引,索引元组仍然保留.

select * from bt_page_items('t01_idx_id',1);